package com.chen.authority.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.authority.common.constant.SysConstant;
import com.chen.authority.common.response.Response;
import com.chen.authority.common.util.WrapperUtil;
import com.chen.authority.common.util.TreeUtil;
import com.chen.authority.mapper.RoleMenuMapper;
import com.chen.authority.model.dto.MenuDto;
import com.chen.authority.model.po.MenuPo;
import com.chen.authority.model.po.RoleMenuPo;
import com.chen.authority.model.qo.MenuQo;
import com.chen.authority.model.vo.MenuVo;
import com.chen.authority.model.vo.MetaVo;
import com.chen.authority.model.vo.RoleVo;
import com.chen.authority.model.vo.RouterVo;
import com.chen.authority.service.MenuService;
import com.chen.authority.service.RoleService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.chen.authority.mapper.MenuMapper;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 菜单权限Service业务层处理
 *
 * @author chengy
 * @date 2022-08-19
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, MenuPo> implements MenuService {

    private final RoleService roleService;
    private final RoleMenuMapper roleMenuMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response addOrUpdateMenu(Integer id, MenuDto dto) {
        boolean notNull = ObjectUtil.isNotNull(id);
        boolean exists = baseMapper.exists(Wrappers.<MenuPo>lambdaQuery()
                .eq(MenuPo::getName, dto.getName())
                .eq(MenuPo::getParentId, dto.getParentId())
                .ne(notNull, MenuPo::getId, id));
        if (exists) {
            return Response.fail("菜单名称重复，请检查");
        }

        MenuPo parentMenu = baseMapper.selectById(dto.getParentId());
        String parentMenuType = null;
        String childrenMenuType = dto.getType();

        if (ObjectUtil.isNotNull(parentMenu)) {
            parentMenuType = parentMenu.getType();
            if (parentMenu.getId().equals(id)) {
                return Response.fail("上级菜单不能为自身");
            }
            if ((childrenMenuType.equals(SysConstant.DIRECTORY)
                    || childrenMenuType.equals(SysConstant.MENU))
                    && !parentMenuType.equals(SysConstant.DIRECTORY)) {
                return Response.fail("目录和菜单的上级菜单只能为目录");
            }

        }
        if (childrenMenuType.equals(SysConstant.BUTTON)
                && !SysConstant.MENU.equals(parentMenuType)) {
            return Response.fail("按钮上级菜单只能为菜单");
        }

        MenuPo menu = dto.toPo(MenuPo.class);

        if (notNull) {
            if (dto.getStatus() == SysConstant.DISABLE) {
                exists = roleMenuMapper.exists(Wrappers.<RoleMenuPo>lambdaQuery()
                        .eq(RoleMenuPo::getMenuId, id));
                if (exists) return Response.fail("该菜单被角色关联，无法禁用");
            }
            menu.setId(id);
        }

        redisTemplate.delete(redisTemplate.keys(SysConstant.MENU_KEY + "*"));

        return this.saveOrUpdate(menu) ? Response.success() : Response.fail();
    }

    @Override
    public Response deleteMenuById(Integer id) {
        List<MenuPo> childrenMenus = baseMapper.selectList(Wrappers
                .<MenuPo>lambdaQuery().eq(MenuPo::getParentId, id));
        if (CollUtil.isNotEmpty(childrenMenus)) {
            return Response.fail("该菜单下存在子菜单，无法删除");
        }

        boolean exists = roleMenuMapper.exists(Wrappers
                .<RoleMenuPo>lambdaQuery().eq(RoleMenuPo::getMenuId, id));
        if (exists) {
            return Response.fail("该菜单或其下的子菜单被角色所关联，无法删除");
        }

        return this.removeById(id) ? Response.success() : Response.fail();
    }

    @Override
    public List<String> findMenuPermissionsByUserId(Object userId) {
        List<Integer> menuIds = getMenuIdsByUserId(userId);
        return CollUtil.isEmpty(menuIds) ?
                null : baseMapper.selectBatchIds(menuIds)
                .stream()
                .map(MenuPo::getPermission)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toList());
    }

    @Override
    public List<MenuVo> findMenusByCondition(MenuQo qo) {
        LambdaQueryWrapper<MenuPo> lqw = WrapperUtil.buildLambdaQueryWrapper(qo);
        boolean emptyOfNormal = lqw.isEmptyOfNormal();
        lqw.orderByAsc(MenuPo::getParentId).orderByAsc(MenuPo::getSort);
        List<MenuVo> menus = baseMapper.selectVoList(lqw);
        if (emptyOfNormal) {
            menus = TreeUtil.build(SysConstant.ROOT, menus);
        } else if (CollUtil.isNotEmpty(menus)) {
            List<MenuVo> menuList = new ArrayList<>(menus);
            TreeUtil.removeSubNodes(menus);
            menus = TreeUtil.build(menus, menuList);
        }
        return menus;
    }

    @Override
    public List<RouterVo> findRoutersByLoginUser() {
        Object loginId = StpUtil.getLoginId();
        List<RouterVo> routers = (List<RouterVo>) redisTemplate.opsForValue().get(SysConstant.MENU_KEY + loginId);
        if (CollUtil.isEmpty(routers)) {
            List<Integer> menuIds = getMenuIdsByUserId(loginId);
            if (CollUtil.isEmpty(menuIds)) {
                return new ArrayList<>();
            }
            List<MenuVo> menus = baseMapper.selectVoBatchIds(menuIds)
                    .stream()
                    .sorted(Comparator.comparingInt(MenuVo::getParentId))
                    .collect(Collectors.toList())
                    .stream()
                    .sorted(Comparator.comparingInt(MenuVo::getSort))
                    .collect(Collectors.toList());
            routers = buildRouters(TreeUtil.build(SysConstant.ROOT, menus));
            redisTemplate.opsForValue().set(SysConstant.MENU_KEY + loginId, routers);
        }
        return routers;
    }

    // 获取指定id用户的菜单id列表
    private List<Integer> getMenuIdsByUserId(Object userId) {
        List<Integer> menuIds = null;
        List<RoleVo> roles = roleService.findRolesByUserId(userId);
        if (CollUtil.isNotEmpty(roles)) {
            List<Integer> roleIds = roles.stream()
                    .map(RoleVo::getId)
                    .collect(Collectors.toList());
            menuIds = roleMenuMapper.selectList(Wrappers
                            .<RoleMenuPo>lambdaQuery()
                            .in(RoleMenuPo::getRoleId, roleIds))
                    .stream()
                    .map(RoleMenuPo::getMenuId)
                    .collect(Collectors.toList());
        }
        return menuIds;
    }

    // 构建前端路由
    private List<RouterVo> buildRouters(List<MenuVo> menus) {
        List<RouterVo> routers = new ArrayList<>();
        for (MenuVo menu : menus) {
            RouterVo router = new RouterVo();
            router.setName(menu.getName());
            router.setPath(menu.getPath());
            router.setHidden(menu.getVisible() != SysConstant.SHOW);
            router.setComponent(menu.getComponent());
            router.setMeta(new MetaVo(menu.getName(), menu.getIcon()));
            List<MenuVo> cMenus = (List<MenuVo>) menu.getChildren();
            if (CollUtil.isNotEmpty(cMenus) && SysConstant.DIRECTORY.equals(menu.getType())) {
                router.setChildren(buildRouters(cMenus));
            }
            routers.add(router);
        }
        return routers;
    }

}
